# -*- coding: utf-8 -*-
"""
@Time ： 2023/4/2 19:17
@Auth ： daiminggao
@File ：Stream.py
@IDE ：PyCharm
@Motto:咕咕咕
"""
import csv
import math

import numpy as np
from scipy.spatial import distance
from scipy.spatial.distance import pdist
from scipy.spatial.distance import squareform
import copy
import matplotlib.pyplot as plt
from sklearn.metrics import adjusted_rand_score
from time import time
from cluster import cluster
import os
import psutil


class Stream:
    def __init__(self, data, dataset_name, alpha, weight_threshold=pow(2, -0.001), max_distance=0.4, min_distance=0.1,
                 time_offline=1):
        self.dataset_name = dataset_name
        self.time_stamp = data[:, -1]  # 时间戳
        self.trueLabels = data[:, -2]
        self.dataList = data[:, :-2]
        self.min_distance = min_distance  # 最小邻域半径
        self.max_distance = max_distance  # 最大邻域半径
        #  min_weight 未确定！！！
        self.min_weight = 60  # 为核心点的最小权
        # self.min_weight = 20  # 为核心点的最小权
        self.time_offline = time_offline  # 集群离线评估周期
        self.alpha = alpha  # 衰减因子0.001
        self.weight_threshold = weight_threshold  # 默认pow(2, -0.007)
        self.point_list = []
        self.num_offline = 0
        self.cluster_result = []

        # self.predictlabel = []

    def fuzzy_query(self, min_distance, max_distance, p, plist):
        if len(plist) <= 1:
            return
        for point in plist:
            if distance.euclidean(p.data, point.data) <= min_distance:
                p.kernel.add(point)
            if (distance.euclidean(p.data, point.data) > min_distance) & (
                    distance.euclidean(p.data, point.data) <= max_distance):
                p.shell.add(point)

    def add_border_point_label(self, plist, C):
        for border_point in plist:
            if bool(border_point.memberships) & (not border_point.is_core):
                label = max(border_point.memberships, key=border_point.memberships.get)
                C.add_cluster(label, border_point)

    def evaluate_core(self, point, t, time_theta):
        weight_sum = math.pow(2, -self.alpha * (t - point.point_time_stamp))
        temp_shell = point.shell.copy()
        for s in temp_shell:
            if s.point_time_stamp < time_theta:
                point.shell.remove(s)
        temp_kernel = point.kernel.copy()
        for k in temp_kernel:
            if k.point_time_stamp < time_theta:
                point.kernel.remove(k)
            else:
                k.weight = math.pow(2, -self.alpha * (t - k.point_time_stamp))
                weight_sum = weight_sum + k.weight

        # print(weight_sum)

        if weight_sum >= self.min_weight:
            point.is_core = True
            return True
        point.is_core = False
        return False

    def fuzzy_border_update(self, border_point, core_point, C):
        label = self.search_label(core_point, C)  # 核心对象所在的簇
        # print("core_point.data,label:", core_point.data, label)
        u = self.fuzzy_membership(border_point, core_point)
        if label in border_point.memberships:
            border_point.memberships[label] = max(border_point.memberships[label], u)
        else:
            border_point.memberships[label] = u
        # print("------------------------------")
        # print("border_point:", border_point.data)
        # print("border_point.memberships:")
        # for key, value in border_point.memberships.items():
        #     print(key, value)
        # print("------------------------------")

    def fuzzy_membership(self, b, c):
        dist = distance.euclidean(b.data, c.data)
        if dist <= self.min_distance:
            return 1
        elif dist < self.max_distance:
            return (self.max_distance - dist) / (self.max_distance - self.min_distance)
        else:
            return 0

    def search_label(self, point_object, C):
        for key, value in C.clusters.items():
            if point_object in value:
                return key

    def show_info(self):
        # 计算消耗内存
        pid = os.getpid()
        # 模块名比较容易理解：获得当前进程的pid
        p = psutil.Process(pid)
        # 根据pid找到进程，进而找到占用的内存值
        info = p.memory_full_info()
        memory = info.uss / 1024 / 1024
        return memory
        # print(f'{start} 一共占用{memory:.2f}MB')

    def show_result(self, clusters, index, plist, start_time, start_memory):
        end_time = time()  # 算法结束时间
        end_memory = self.show_info()
        elapesd = end_time - start_time
        memory = end_memory - start_memory
        plt.figure(figsize=(8, 4))
        colors = ['black', 'red', 'green', 'blue', 'yellow', 'pink', 'coral',
                  'brown',
                  ]
        i = 0
        sum = 0
        pre_labels = []
        true_labels = []
        datalist = self.dataList.tolist()
        for key in clusters:
            for point in clusters[key]:
                pre_labels.append(key)
                true_labels.append(self.trueLabels[datalist.index(point.data.tolist())])
                if not point.is_core:
                    # print(point.memberships)
                    plt.scatter(point.data[0], point.data[1], c=colors[i], label=i, marker='o')  # 边界对象
                else:
                    plt.scatter(point.data[0], point.data[1], c=colors[i], label=i, marker='+')  # 核心对象
                sum += 1
            i = i + 1

        # #########################  compute purity,accuracy

        label_set = set()
        clu = {}
        for i in range(0, len(pre_labels)):
            if pre_labels[i] not in label_set:
                label_set.add(pre_labels[i])
                clu[pre_labels[i]] = {
                    "num": 0,
                    "label": []
                }
            clu[pre_labels[i]]["num"] += 1
            clu[pre_labels[i]]["label"].append(true_labels[i])
        numOfClusters = len(label_set)
        puritySum = 0
        numOfSamples = 0
        CdSum = 0
        for c in clu.values():
            numOfSamples += c["num"]
            a = max(c["label"], key=c["label"].count)
            Cd = c["label"].count(a)
            puritySum += Cd / c["num"]
            CdSum += Cd
        mean_purity = puritySum / numOfClusters
        accuracy = CdSum / numOfSamples
        ari = adjusted_rand_score(true_labels, pre_labels)

        # ############################################
        with open('./result/zhao/kddcup(reduce).csv', newline='',
                  mode='a+') as f:
            writer = csv.writer(f)
            row = [mean_purity, accuracy, elapesd, memory]
            # row = [numOffmics, ari, mean_purity, accuracy]
            writer.writerow(row)

        print("sum of point:", sum)
        plt.title(index)
        # plt.legend()
        path = "./result/" + self.dataset_name + "(zhao)"
        if not os.path.exists(path):
            os.mkdir(path)
        plt.savefig(path + "/" + str(index) + ".png")
        plt.close()

        print("ari:", ari)
        print("mean_purity:", mean_purity)
        print("accuracy:", accuracy)
        print("time:", elapesd)
        print("memory:", memory)

    def offline_FDBSCAN(self, plist, t, start_time, satrt_memory):
        global cluster_label
        C = cluster(str(self.num_offline))
        cluster_count = 0
        time_theta = t + math.log2(self.weight_threshold) / self.alpha
        print("time_theta=" + str(time_theta))
        temp_plist = plist.copy()
        for point in temp_plist:
            if point.point_time_stamp < time_theta:
                plist.remove(point)
            else:
                point.memberships = {}
                point.is_core = False
        visited = set()
        print("==============plist=========" + str(len(plist)))
        for point in plist:
            to_visit = set()
            first = True
            if point not in visited:
                to_visit = to_visit | {point, }
                while True:
                    to_visit_num = len(to_visit)
                    for to_visit_point in to_visit:
                        if self.evaluate_core(to_visit_point, t, time_theta):
                            # to_visit_point.is_core = True
                            if to_visit_point not in visited:
                                visited = visited | {to_visit_point, }
                                if first:
                                    cluster_label = "C_" + str(self.num_offline) + "_" + str(cluster_count)
                                    cluster_count = cluster_count + 1
                                    # C = cluster(cluster_label)
                                    first = False
                                C.add_cluster(cluster_label, to_visit_point)
                                to_visit = to_visit | to_visit_point.kernel
                                for s in to_visit_point.kernel | to_visit_point.shell:
                                    # if not s.is_core:
                                    if not self.evaluate_core(s, t, time_theta):
                                        self.fuzzy_border_update(s, to_visit_point, C)
                    new_to_visit_num = len(to_visit)
                    if new_to_visit_num == to_visit_num:
                        break
        self.add_border_point_label(plist, C)
        # for point_object in self.point_list:
        #     print(point_object.is_core)
        # print("_______________")
        # print(C.clusters)
        # print("_______________")
        self.cluster_result.append(C.clusters)
        self.show_result(C.clusters, self.num_offline, plist, start_time, satrt_memory)
        # ari = adjusted_rand_score(self.truelabel, self.predictlabel)

        # record = True
        # if record:
        #     with open('./result/ARI/' + self.dataset_name + '.csv', newline='', mode='a+') as f:
        #         # create the csv writer
        #         writer = csv.writer(f)
        #         row = [ari]
        #         writer.writerow(row)
        # print(self.truelabel)
        # print(self.predictlabel)
